#region Copyright
/// <copyright>
/// Copyright (c) 2007 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Osh.Scripting
{
	/// <summary>
	/// Provides access to managed object events from javascript code.
	/// Events should have one of the following delegates:
	///		delegate void EventHandler(object sender, EventArgs args); -or-
	///		delegate void EventHandler&lt;TEventArgs&gt;(object sender, TEventArgs args);
	/// </summary>
	public class EventSource
	{
		private static List<EventSource> _events = new List<EventSource>();
		private object _object;
		private string _eventName;
		private string _callback;
		private Delegate _handler;
		private object _eventSender;
		private EventArgs _eventArgs;
		
		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="obj">Managed object which generate events.</param>
		/// <param name="eventName">Name of an event this object will attach to.</param>
		/// <param name="callback">Javascript code which will be called when event occurs.</param>
		public EventSource(object obj, string eventName, string callback)
		{
			_object = obj;
			_eventName = eventName;
			_callback = callback;
			_handler = Attach(obj, eventName);
		}

		/// <summary>
		/// Provides access to the event sender during the callback.
		/// </summary>
		public object EventSender
		{
			get { return _eventSender; }
		}

		/// <summary>
		/// Provides access to the event arguments during the callback.
		/// </summary>
		public EventArgs EventArgs
		{
			get { return _eventArgs; }
		}

		private Delegate Attach(object obj, string eventName)
		{
			if(string.IsNullOrEmpty(eventName))
				throw new ArgumentOutOfRangeException("eventName");

			EventInfo eventInfo = obj.GetType().GetEvent(eventName);
			if(eventInfo == null)
				throw new ArgumentOutOfRangeException(eventName, "Object does not have such event.");
			
			MethodInfo method = typeof(EventSource).GetMethod("OnEvent", BindingFlags.NonPublic | BindingFlags.Instance);
			Type eventHandlerType = eventInfo.EventHandlerType;
			Delegate eventHandler = Delegate.CreateDelegate(eventHandlerType, this, method, true);

			eventInfo.AddEventHandler(obj, eventHandler);
			lock(_events)
				_events.Add(this);
			return eventHandler;
		}

		/// <summary>
		/// Detaches from the event.
		/// </summary>
		public void Detach()
		{
			Detach(true);
		}

		private void Detach(bool removeFromList)
		{
			if(_handler != null)
			{
				EventInfo eventInfo = _object.GetType().GetEvent(_eventName);
				eventInfo.RemoveEventHandler(_object, _handler);
				_handler = null;
				if(removeFromList)
					lock(_events)
						_events.Remove(this);
			}
		}

		private void OnEvent(object sender, EventArgs args)
		{
			_eventSender = sender;
			_eventArgs = args;
			if(OshHostBase.Current != null)
				OshHostBase.Current.InvokeScript("eval", _callback);
			_eventSender = null;
			_eventArgs = null;
		}

		public static void DetachAllEvents()
		{
			foreach(EventSource eventSource in _events)
				eventSource.Detach(false);
			_events.Clear();
		}
	}

}
